package com.nervenets.template.service.impl;

import com.nervenets.general.Global;
import com.nervenets.general.redis.RedisSyncLocker;
import com.nervenets.general.service.impl.BaseServiceImpl;
import com.nervenets.general.utils.NumberUtil;
import com.nervenets.general.utils.SpringContextHolder;
import com.nervenets.template.enumeration.*;
import com.nervenets.template.event.DataOfMoneyEvent;
import com.nervenets.template.exception.BalanceNotEnoughException;
import com.nervenets.template.hibernate.dao.WealthFlowDao;
import com.nervenets.template.hibernate.domain.user.WealthFlow;
import com.nervenets.template.service.WealthFlowService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class WealthFlowServiceImpl extends BaseServiceImpl<WealthFlow, WealthFlowDao> implements WealthFlowService {
    @Resource
    private RedisSyncLocker<Map> redisSyncLocker;

    @Override
    @Cacheable(value = WealthFlow.CACHE_KEY, key = "@us.upperCase(#wealthType.name()) + ':' + #userId", sync = true)
    public long getSurplus(long userId, WealthType wealthType) {
        final WealthFlow top = dao.findTop1ByUserIdAndWealthTypeOrderByIdDesc(userId, wealthType);
        return null == top ? 0L : top.getSurplus();
    }

    @Override
    @Cacheable(value = WealthFlow.CACHE_KEY, key = "@us.upperCase(#wealthType.name()) + ':map:' + #userId", sync = true)
    public Map getMap(long userId, WealthType wealthType) {
        List<Map> maps = dao.findWealthMapByUserId(userId, wealthType);
        Map<Object, Object> result = new HashMap<>();
        maps.forEach(map -> result.put(map.get("operateType"), map.get("total")));
        return result;
    }

    @SneakyThrows
    @Override
    @Transactional
    @CachePut(value = WealthFlow.CACHE_KEY, key = "@us.upperCase(#wealthType.name()) + ':map:' + #userId")
    @CacheEvict(value = WealthFlow.CACHE_KEY, key = "@us.upperCase(#wealthType.name()) + ':' + #userId")
    public Map wealthChange(long userId, long offset, WealthType wealthType, OperateType operateType, String detail, PayType payType, long ip, long unionId, UnionType unionType, FlowStatus flowStatus) {
        return redisSyncLocker.keyLock(String.format(Global.Constants.REDIS_USER_WEALTH_LOCK, userId), () -> {

            log.info(String.format("正在给用户【%s】进行账户变动", userId));

            WealthFlow lastFlow = dao.findTop1ByUserIdAndWealthTypeOrderByIdDesc(userId, wealthType);
            long surplus = null == lastFlow ? 0 : lastFlow.getSurplus();
            long total = (null == lastFlow ? 0 : lastFlow.getTotal());
            if (offset > 0 && isTrueIncome(operateType)) {
                total += offset;
            }

            final long newSurplus = surplus + offset;
            if (newSurplus < 0) {
                throw new BalanceNotEnoughException(String.format("会员【%s】余额变动失败，余额不足", userId));
            }
            log.info(String.format("用户%s上次结余%s元，%s变动%s后剩余%s元", userId, NumberUtil.fen2Yuan(surplus), operateType, NumberUtil.fen2Yuan(offset), NumberUtil.fen2Yuan(newSurplus)));
            WealthFlow flow = new WealthFlow(userId, offset, newSurplus, operateType, ip, unionId, unionType, detail);
            flow.setWealthType(wealthType);
            flow.setFlowStatus(flowStatus);
            flow.setPayType(payType);
            flow.setTotal(total);
            super.save(flow);

            SpringContextHolder.publishEvent(new DataOfMoneyEvent(operateType));

            return getMap(userId, wealthType);
        });
    }

    @Override
    public long sumOf(WealthType wealthType, List<OperateType> operateTypes, long startTime, long endTime) {
        return dao.sumOf(wealthType, operateTypes, startTime, endTime).longValue();
    }

    private boolean isTrueIncome(OperateType operateType) {
        return OperateType.recharge.equals(operateType);
    }
}
